package com.xxxtea.component;

import cn.hutool.core.io.FileUtil;
import com.xxxtea.util.KryoSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
 * 本地持久化缓存组件
 * 
 * 提供基于ConcurrentHashMap的本地缓存功能，支持应用重启后的数据持久化。
 * - 支持缓存的自动加载与销毁时持久化
 * - 支持懒加载与缓存缺失时的回调填充
 * - 线程安全
 * 
 * @author badman
 * @since 1.0.0
 */
@Slf4j
@Component
public class LocalCache implements DisposableBean {
	/** 缓存持久化文件路径，从配置文件读取 */
	@Value("${cachePath}")
	private String cachePath;
	/** 缓存容器 */
	private ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();

	/**
	 * 初始化方法，应用启动时自动加载本地缓存文件
	 */
	@PostConstruct
	private void init() {
		if (!FileUtil.exist(cachePath)) {
			return;
		}
		try {
			this.cache = KryoSerializer.readObject(new File(cachePath));
		} catch (Exception e) {
			log.error("加载LocalCache出现异常", e);
		}
	}

	/**
	 * 向缓存中放入数据
	 * @param key 缓存键
	 * @param value 缓存值
	 */
	public void put(String key, Object value) {
		cache.put(key, value);
	}

	/**
	 * 获取缓存数据
	 * @param key 缓存键
	 * @return 缓存值
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(String key) {
		return (T) cache.get(key);
	}

	/**
	 * 获取缓存数据，若不存在则通过回调加载并写入缓存
	 * @param key 缓存键
	 * @param supplier 缓存缺失时的加载回调
	 * @return 缓存值
	 */
	public <T> T getIfAbsent(String key, Supplier<T> supplier) {
		T value = this.get(key);
		if (value == null) {
			value = supplier.get();
			put(key, value);
		}
		return value;
	}

	/**
	 * Spring容器销毁时自动调用，持久化缓存到本地文件
	 */
	@Override
	public void destroy() throws Exception {
		KryoSerializer.writeObject(new File(cachePath), this.cache);
		log.info("成功写入数据到: {}", cachePath);
	}
}
